function MovingUnitFiniteStateMachine(unit) {
	this.unit = unit;
	
	this.movingUnitStatesEnum = {
		STAND:10,
		CALCULATE_PATH:20,
		EVALUATE_PATH_ADVANCEMENT:30,
		MOVE_BETWEEN_PATH_NODES:40,
		WAIT_FOR_BETTER_PATH:50
	};
	
	this.movingUnitStates = new Array();
	this.movingUnitStates[this.movingUnitStatesEnum.STAND] = new MovingUnitStandState(this.unit);
	this.movingUnitStates[this.movingUnitStatesEnum.CALCULATE_PATH] = new MovingUnitCalculatePathState(this.unit);
	this.movingUnitStates[this.movingUnitStatesEnum.EVALUATE_PATH_ADVANCEMENT] = new MovingUnitEvaluatePathAdvancementState(this.unit);
	this.movingUnitStates[this.movingUnitStatesEnum.MOVE_BETWEEN_PATH_NODES] = new MovingUnitMoveBetweenPathNodesState(this.unit);
	this.movingUnitStates[this.movingUnitStatesEnum.WAIT_FOR_BETTER_PATH] = new MovingUnitWaitForBetterPathState(this.unit);
	
	this.currentState = this.movingUnitStates[this.movingUnitStatesEnum.STAND];
	
	this.onUnitProcessInput = function(inputEvent) {
		if(inputEvent instanceof MoveInputEvent) {
			this.movingUnitStates[this.movingUnitStatesEnum.STAND].globalStateDurationMs = 0;
			this.movingUnitStates[this.movingUnitStatesEnum.CALCULATE_PATH].globalStateDurationMs = 0;
			this.movingUnitStates[this.movingUnitStatesEnum.EVALUATE_PATH_ADVANCEMENT].globalStateDurationMs = 0;
			this.movingUnitStates[this.movingUnitStatesEnum.MOVE_BETWEEN_PATH_NODES].globalStateDurationMs = 0;
			this.movingUnitStates[this.movingUnitStatesEnum.WAIT_FOR_BETTER_PATH].globalStateDurationMs = 0;
		}
		this.currentState.base_onUnitProcessInput(inputEvent);
	};
	
	this.onUnitUpdateState = function() {
		var nextState = this.currentState.base_onUnitUpdateState();
		if(nextState !==  this.currentState) {
			nextState.init();
		}
		this.currentState = nextState;
	};
}

function MovingUnitBaseState(unit) {
	this.unit = unit;
	
	this.globalStateDurationMs = 0;
	
	this.init = function() {};
	
	this.base_onUnitProcessInput = function(inputEvent) {
		var st = new Date().getTime();
		this.onUnitProcessInput(inputEvent);
		this.globalStateDurationMs += new Date().getTime()-st;
	};
	
	this.onUnitProcessInput = function(inputEvent) {};
	
	this.base_onUnitUpdateState = function() {
		var st = new Date().getTime();
		var curState = this.onUnitUpdateState();
		this.globalStateDurationMs += new Date().getTime()-st;
		return curState;
	};
	
	this.onUnitUpdateState = function() {};
	
	this.getState = function(movingUnitStatesEnum) {
		var state = this.unit.finiteStateMachine.movingUnitStates[movingUnitStatesEnum];
		var argsArray = Array.prototype.slice.call(arguments);
		if(argsArray.length > 1) {
			argsArray.splice(0,1);
			state.init.apply(state, argsArray);
		}
		return state;
	};
	
	this.getStandState = function() {
		return this.getState(this.unit.finiteStateMachine.movingUnitStatesEnum.STAND); 
	};
	
	this.getCalculatePathState = function() {
		return this.getState(this.unit.finiteStateMachine.movingUnitStatesEnum.CALCULATE_PATH);
	};
	
	this.getEvaluatePathAdvancementState = function(path) {
		return this.getState(this.unit.finiteStateMachine.movingUnitStatesEnum.EVALUATE_PATH_ADVANCEMENT, path);
	};

	this.getMoveBetweenPathNodesState = function(currentNode, destinationNode) {
		return this.getState(this.unit.finiteStateMachine.movingUnitStatesEnum.MOVE_BETWEEN_PATH_NODES, currentNode, destinationNode);
	};
	
	this.getWaitForBetterPathState = function() {
		return this.getState(this.unit.finiteStateMachine.movingUnitStatesEnum.WAIT_FOR_BETTER_PATH);
	};
	
	this.setStandAnimation = function() {
		if(DEBUG_MODE) {
			if(this.unit.destinationCellX > CANVAS_CELLS_X/2) {
				this.unit.currentAnimation = this.unit.animationBundle.debugRight;
			} else {
				this.unit.currentAnimation = this.unit.animationBundle.debugLeft;
			}
		} else {
			//TODO
			this.unit.currentAnimation = this.unit.animationBundle.standDown;
		}
		this.unit.currentAnimation.start();
	};
	
	this.setWalkAnimation = function() {
		if(DEBUG_MODE) {
			if(this.unit.direction.x == 1) {
				this.unit.currentAnimation = this.unit.animationBundle.debugRight;
			} else {
				this.unit.currentAnimation = this.unit.animationBundle.debugLeft;
			}
		} else {
			if(this.unit.direction.x == 1) {
				this.unit.currentAnimation = this.unit.animationBundle.walkRight;
			} else if(this.unit.direction.x == -1) {
				this.unit.currentAnimation = this.unit.animationBundle.walkLeft;
			} else {
				if(this.unit.direction.y == 1) {
					this.unit.currentAnimation = this.unit.animationBundle.walkDown;
				} else {
					this.unit.currentAnimation = this.unit.animationBundle.walkUp;
				}
			}
			
		}
		if( !this.unit.currentAnimation.isStarted() ) {
			this.unit.currentAnimation.start();
		}		
	};
}

function MovingUnitStandState(unit) {
	
	$.extend(this,new MovingUnitBaseState(unit));
	
	this.goToCalculatePathState = false;
	
	this.init = function() {
		this.setStandAnimation();		
	};
	
	this.onUnitProcessInput = function(inputEvent) {
		if(inputEvent instanceof MoveInputEvent) {
			this.goToCalculatePathState = true;	
		}		
	};
	
	this.onUnitUpdateState = function() {
		if(this.goToCalculatePathState) {
			this.goToCalculatePathState = false;
			return this.getCalculatePathState();
		} else {
			return this;
		}
	};
}

function MovingUnitCalculatePathState(unit) {
	
	$.extend(this,new MovingUnitBaseState(unit));
	
	this.aStar = new AStar();
	
	this.init = function() {
		this.setStandAnimation();		
	};
	
	this.onUnitUpdateState = function() {
		var path = this.aStar.getPath(this.unit.currentX / this.unit.cellSize, this.unit.currentY / this.unit.cellSize,
				this.unit.destinationCellX, this.unit.destinationCellY,
				this.unit.area, DISTANCE_CALCULATOR, 3);
		this.unit.totalPathCalculatingDurationMs += path.computeTimeMs;
		return this.getEvaluatePathAdvancementState(path);
	};
}

function MovingUnitEvaluatePathAdvancementState(unit) {
	
	$.extend(this,new MovingUnitBaseState(unit));
	
	this.path = null;
	this.currentNodeIdx = 0;
	this.hasWaitedForBetterPath = false;
	this.maxPathRecalculations = 3;
	this.recalculatePathCount = 0;
	
	this.init = function(path) {
		if(path!=null) {
			this.currentNodeIdx = 0;
			this.path = path;
			this.recalculatePathCount = 0;
		}
	};
	
	this.onUnitUpdateState = function() {
		if(this.isAtDestination()) {
			//DEBUG_CONSOLE.toConsole("This guy spent on path (re)calculation: "+this.unit.totalPathCalculatingDurationMs+" ms.");
			this.unit.manager.onGameEntityReachedDestionation(this.unit);
			return this.getStandState();
		} else if(this.isAtLastPathNode()) {
			if(!this.hasWaitedForBetterPath) {
				this.hasWaitedForBetterPath = true;
				return this.getWaitForBetterPathState();
			} else {
				this.hasWaitedForBetterPath = false;
				this.recalculatePathCount++;
				//TODO quit after 3 path recalculations for the same starting position
				return this.getCalculatePathState();
			}
		} else if(this.isNextNodeAtLeastSomewhatPassable()) {
			var currentNode = this.path.nodes[this.currentNodeIdx];
			this.currentNodeIdx++;
			var destinationNode = this.path.nodes[this.currentNodeIdx];
			return this.getMoveBetweenPathNodesState(currentNode, destinationNode);
		} else if(!this.isNextNodeAtLeastSomewhatPassable()) {
			if(!this.hasWaitedForBetterPath) {
				this.hasWaitedForBetterPath = true;
				return this.getWaitForBetterPathState();
			} else {
				this.hasWaitedForBetterPath = false;
				//TODO max recalculations;
				return this.getCalculatePathState();
			}
		}
	};
	
	this.isAtLastPathNode = function() {
		return this.currentNodeIdx == this.path.nodes.length-1;
	};
	
	this.isAtDestination = function() {
		return this.unit.currentX/this.unit.cellSize == this.unit.destinationCellX && 
			this.unit.currentY/this.unit.cellSize == this.unit.destinationCellY;
	};
	
	this.isNextNodeAtLeastSomewhatPassable = function() {
		var nextNode = this.path.nodes[this.currentNodeIdx+1];
		var area = this.unit.area;
		var nextNodeTerrainType = area.getTerrainType(nextNode.x, nextNode.y);
		return nextNodeTerrainType == TerrainType.PASSABLE || nextNodeTerrainType == TerrainType.SOMEWHAT_PASSABLE;
	};
}

function MovingUnitMoveBetweenPathNodesState(unit) {
	
	$.extend(this,new MovingUnitBaseState(unit));
	
	this.currentNode = null;
	this.destinationNode = null;
	
	this.destinationX = -1;
	this.destinationY = -1;
	
	this.speed;
	
	this.init = function(currentNode, destinationNode) {
		if(currentNode != null && destinationNode != null) {
			this.currentNode = currentNode;
			this.destinationNode = destinationNode;
			
			this.unit.direction.update(currentNode.x, currentNode.y, destinationNode.x, destinationNode.y);
			
			this.speed = this.unit.direction.isDiagonal() ? this.unit.diagonalSpeed : this.unit.speed;
			
			this.destinationX = this.destinationNode.x * this.unit.cellSize;
			this.destinationY = this.destinationNode.y * this.unit.cellSize;
			
			var area = this.unit.area;
			area.edit(this.currentNode.x, this.currentNode.y, TerrainType.IMPASSABLE);
			area.edit(this.destinationNode.x, this.destinationNode.y, TerrainType.IMPASSABLE);
			
			this.setWalkAnimation();
		}		
	};
	
	this.onUnitUpdateState = function() {
		if(!this.hasReachedDestinationNode()) {
			var diffX = this.destinationX - this.unit.currentX;
			if(this.isAtLessThanOneUpdateFromDestinationX()) {
				this.jumpToDestinationX();
			} else {
				this.unit.currentX += this.unit.direction.x * this.speed;
				if(this.isAtLessThanOrExactlyHalfUpdateFromDestinationX()) {
					this.jumpToDestinationX();
				}
			}
			
			var diffY = this.destinationY - this.unit.currentY;
			if(this.isAtLessThanOneUpdateFromDestinationY()) {
				this.jumpToDestinationY();
			} else {
				this.unit.currentY += this.unit.direction.y * this.speed;
				if(this.isAtLessThanOrExactlyHalfUpdateFromDestinationY()) {
					this.jumpToDestinationY();
				}
			}
			return this;
		} else {
			this.unit.area.edit(this.currentNode.x, this.currentNode.y, TerrainType.PASSABLE);
			return this.getEvaluatePathAdvancementState();
		}
	};
	
	this.hasReachedDestinationNode = function() {
		return this.unit.currentX == this.destinationNode.x * this.unit.cellSize && 
			this.unit.currentY == this.destinationNode.y * this.unit.cellSize;
	};
	
	this.isAtLessThanOrExactlyHalfUpdateFromDestinationX = function() {
		var diffX = this.destinationX - this.unit.currentX;
		return (Math.abs(diffX)*this.unit.cellSize)/2 <= this.speed;
	};
	
	this.isAtLessThanOrExactlyHalfUpdateFromDestinationY = function() {
		var diffY = this.destinationY - this.unit.currentY;
		return (Math.abs(diffY)*this.unit.cellSize)/2 <= this.speed;
	};
	
	this.isAtLessThanOneUpdateFromDestinationX = function() {
		var diffX = this.destinationX - this.unit.currentX;
		return Math.abs(diffX)*this.unit.cellSize < this.speed; 
	};
	
	this.isAtLessThanOneUpdateFromDestinationY = function() {
		var diffY = this.destinationY - this.unit.currentY;
		return Math.abs(diffY)*this.unit.cellSize <= this.speed;
	};
	
	this.jumpToDestinationX = function() {
		this.unit.currentX = this.destinationNode.x * this.unit.cellSize;
	};
	
	this.jumpToDestinationY = function() {
		this.unit.currentY = this.destinationNode.y * this.unit.cellSize;
	};
}

function MovingUnitWaitForBetterPathState(unit) {
	$.extend(this,new MovingUnitBaseState(unit));
	
	this.stateUpdatesCount = 0;
	
	this.init = function() {
		this.setStandAnimation();
	};
	
	this.onUnitUpdateState = function() {
		this.stateUpdatesCount++;
		if(this.stateUpdatesCount >= UPDATES_TO_WAIT_UNTIL_PATH_RECALCULATION) {
			this.stateUpdatesCount = 0;
			return this.getEvaluatePathAdvancementState();
		} else {
			return this;
		}
	};
}